// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 此文件实现源位置的编码。

package src

import (
	"bytes"
	"fmt"
	"io"
)

// Pos编码由（行、列）数字对组成的源位置
// 和一个位置基准。零位是一个随时可用的“未知”位置（零
// 定位基准线和零线编号）。
// None
// （行、列）值是指文件中独立于任何
// 位置基准（“绝对”文件位置）。
// None
// 位置基准用于确定“相对”位置，即
// 相对于位置基准的文件名和行号。如果基数为
// 对于当前文件，绝对和相对之间没有区别
// 位置。如果它指的是
// 遵守该指令。位置基准依次包含其所在的位置
// 已在当前文件中引入。
type Pos struct {
	base *PosBase
	lico
}

// NoPos是一个有效的未知位置。
var NoPos Pos

// MakePos使用给定的基数和（文件绝对值）创建新的Pos值
// 行和列。
func MakePos(base *PosBase, line, col uint) Pos {
	return Pos{base, makeLico(line, col)}
}

// IsKnown报告位置p是否已知。
// 如果某个位置具有非零，则该位置是已知的
// 位置基准，或非零行号。
func (p Pos) IsKnown() bool {
	return p.base != nil || p.Line() != 0
}

// Before报告源中位置p是否在q之前。
// 对于不同文件中的位置，按文件名排序。
func (p Pos) Before(q Pos) bool {
	n, m := p.Filename(), q.Filename()
	return n < m || n == m && p.lico < q.lico
}

// After报告源中的位置p是否在q之后。
// 对于不同文件中的位置，按文件名排序。
func (p Pos) After(q Pos) bool {
	n, m := p.Filename(), q.Filename()
	return n > m || n == m && p.lico > q.lico
}

func (p Pos) LineNumber() string {
	if !p.IsKnown() {
		return "?"
	}
	return p.lico.lineNumber()
}

func (p Pos) LineNumberHTML() string {
	if !p.IsKnown() {
		return "?"
	}
	return p.lico.lineNumberHTML()
}

// Filename返回包含此位置的实际文件的名称。
func (p Pos) Filename() string { return p.base.Pos().RelFilename() }

// Base返回位置Base。
func (p Pos) Base() *PosBase { return p.base }

// “收进”设置位置基准。
func (p *Pos) SetBase(base *PosBase) { p.base = base }

// RelFilename返回以位置为基准记录的文件名。
func (p Pos) RelFilename() string { return p.base.Filename() }

// RelLine返回相对于位置基准的行号。
func (p Pos) RelLine() uint {
	b := p.base
	if b.Line() == 0 {
		// 基线未知=>相对线未知
		return 0
	}
	return b.Line() + (p.Line() - b.Pos().Line())
}

// RelCol返回相对于位置基准的列号。
func (p Pos) RelCol() uint {
	b := p.base
	if b.Col() == 0 {
		// 基列未知=>相对列未知
		// （行指令的当前规范要求
		// 这将一直应用到下一个PosBase/line指令，
		// 不只是在新的换行之前）
		return 0
	}
	if p.Line() == b.Pos().Line() {
		// 与p的基在同一行的p=>列相对于p的基
		return b.Col() + (p.Col() - b.Pos().Col())
	}
	return p.Col()
}

// AbsFilename（）返回以位置为基准记录的绝对文件名。
func (p Pos) AbsFilename() string { return p.base.AbsFilename() }

// SymFilename（）返回位置基记录的绝对文件名，
// 以FileSymPrefix作为前缀，使其适合用作链接器符号。
func (p Pos) SymFilename() string { return p.base.SymFilename() }

func (p Pos) String() string {
	return p.Format(true, true)
}

// 将位置格式化为“文件名：行”或“文件名：行：列”，
// 由showCol标志控制，如果列已知（！=0）。
// 对于相对于行指令的位置，原始位置为
// 如“filename:line[origfile:origline:origcolumn]所示，如果
// showOrig已设置。
func (p Pos) Format(showCol, showOrig bool) string {
	buf := new(bytes.Buffer)
	p.WriteTo(buf, showCol, showOrig)
	return buf.String()
}

// 将某个位置写入w，格式与Format相同。
func (p Pos) WriteTo(w io.Writer, showCol, showOrig bool) {
	if !p.IsKnown() {
		io.WriteString(w, "<unknown line number>")
		return
	}

	if b := p.base; b == b.Pos().base {
		// base是文件库（包括nil）
		format(w, p.Filename(), p.Line(), p.Col(), showCol)
		return
	}

	// 基础是相对的
	// 仅打印自
	// 相对位置的列信息可能是虚假的（它是
	// 通常生成代码，我们不能对
	// 该点的原始源，但对于文件：行信息
	// 这是通过行指令提供的）。
	// TODO（gri）如果我们有内联基础，这可能不是真的。
	// 我们可能希望在某一点上有所区别。
	format(w, p.RelFilename(), p.RelLine(), p.RelCol(), showCol)
	if showOrig {
		io.WriteString(w, "[")
		format(w, p.Filename(), p.Line(), p.Col(), showCol)
		io.WriteString(w, "]")
	}
}

// 将（文件名、行、列）元组格式化为“文件名：行”（showCol）
// 为false或col==0）或“filename:line:column”（showCol为true且col！=0）。
func format(w io.Writer, filename string, line, col uint, showCol bool) {
	io.WriteString(w, filename)
	io.WriteString(w, ":")
	fmt.Fprint(w, line)
	// col==0和col==colMax被解释为未知列值
	if showCol && 0 < col && col < colMax {
		io.WriteString(w, ":")
		fmt.Fprint(w, col)
	}
}

// formatstr包装格式以返回字符串。
func formatstr(filename string, line, col uint, showCol bool) string {
	buf := new(bytes.Buffer)
	format(buf, filename, line, col, showCol)
	return buf.String()
}

// ----------------------------------------------------------------------------
// PosBase

// PosBase对文件名和基本位置进行编码。
// 通常，每个文件和行指令都会引入一个PosBase。
type PosBase struct {
	pos         Pos    // 相对位置所在的位置（直线、柱）
	filename    string // 用于打开源文件的文件名，用于显示错误消息
	absFilename string // 绝对文件名，用于PC行表
	symFilename string // 缓存的符号文件名，以避免重复的字符串连接
	line, col   uint   // 相对行，位置处的列号
	inl         int    // 内联索引（参见cmd/internal/obj/inl.go）
}

// NewFileBase为具有给定（相对）和
// 绝对值）文件名。
func NewFileBase(filename, absFilename string) *PosBase {
	base := &PosBase{
		filename:    filename,
		absFilename: absFilename,
		symFilename: FileSymPrefix + absFilename,
		line:        1,
		col:         1,
		inl:         -1,
	}
	base.pos = MakePos(base, 1, 1)
	return base
}

// NewLinePragmaBase为以下形式的行指令返回一个新的*PosBase
// None
// /*行文件名：行：列*/
// 在位置。
func NewLinePragmaBase(pos Pos, filename, absFilename string, line, col uint) *PosBase {
	return &PosBase{pos, filename, absFilename, FileSymPrefix + absFilename, line, col, -1}
}

// NewInlineBase返回具有给定内联的旧PosBase的副本
// 指数如果old==nil，则生成的PosBase没有文件名。
func NewInliningBase(old *PosBase, inlTreeIndex int) *PosBase {
	if old == nil {
		base := &PosBase{line: 1, col: 1, inl: inlTreeIndex}
		base.pos = MakePos(base, 1, 1)
		return base
	}
	copy := *old
	base := &copy
	base.inl = inlTreeIndex
	if old == old.pos.base {
		base.pos.base = base
	}
	return base
}

var noPos Pos

// Pos返回底座所在的位置。
// 如果b==nil，则结果为零位置。
func (b *PosBase) Pos() *Pos {
	if b != nil {
		return &b.pos
	}
	return &noPos
}

// Filename返回与基文件一起记录的文件名。
// 如果b==nil，则结果为空字符串。
func (b *PosBase) Filename() string {
	if b != nil {
		return b.filename
	}
	return ""
}

// AbsFilename返回与基文件一起记录的绝对文件名。
// 如果b==nil，则结果为空字符串。
func (b *PosBase) AbsFilename() string {
	if b != nil {
		return b.absFilename
	}
	return ""
}

const FileSymPrefix = "gofile.."

// SymFilename返回与基文件一起记录的绝对文件名，
// 以FileSymPrefix作为前缀，使其适合用作链接器符号。
// 如果b为nil，SymFilename将返回FileSymPrefix+“？？”。
func (b *PosBase) SymFilename() string {
	if b != nil {
		return b.symFilename
	}
	return FileSymPrefix + "??"
}

// Line返回与基一起记录的行号。
// 如果b==nil，则结果为0。
func (b *PosBase) Line() uint {
	if b != nil {
		return b.line
	}
	return 0
}

// Col返回与基一起记录的列号。
// 如果b==nil，则结果为0。
func (b *PosBase) Col() uint {
	if b != nil {
		return b.col
	}
	return 0
}

// InliningIndex将索引返回到全局内联索引中
// 用底座记录的树。如果b==nil或基数为
// 未内联，结果小于0。
func (b *PosBase) InliningIndex() int {
	if b != nil {
		return b.inl
	}
	return -1
}

// ----------------------------------------------------------------------------
// 历高

// lico是行号和列号的压缩编码。
type lico uint32

// 布局常数：行为20位，列为8位，isStmt为2位，pro/epilogue为2位
// （如果这太紧，我们可以使lico 64b宽，
// 或者我们可以引入一种分层编码，从中删除列
// 随着行号的增加，信息量也越来越大；类似于什么
// 是的。）
// 选择位字段顺序以使IsStmt的重要性最低
// 职位的一部分；它的用途是通过
// 代码生成中的指令加扰，而不是强加命令。
// TODO:序幕和尾声作为汇编程序的伪操作处理可能更好，
// 因为它们几乎没有与位置的其他用途交互。
const (
	lineBits, lineMax     = 20, 1<<lineBits - 2
	bogusLine             = 1 // 用于中断无限循环以防止调试器循环
	isStmtBits, isStmtMax = 2, 1<<isStmtBits - 1
	xlogueBits, xlogueMax = 2, 1<<xlogueBits - 1
	colBits, colMax       = 32 - lineBits - xlogueBits - isStmtBits, 1<<colBits - 1

	isStmtShift = 0
	isStmtMask  = isStmtMax << isStmtShift
	xlogueShift = isStmtBits + isStmtShift
	xlogueMask  = xlogueMax << xlogueShift
	colShift    = xlogueBits + xlogueShift
	lineShift   = colBits + colShift
)
const (
	// 预计SSA中的前端或阶段通常会生成标记有
	// PosDefaultStmt，但请注意PosIsStmt的语句边界。简单语句将有一个
	// 边界对于具有初始化的循环，一个用于其入口，一个用于其后缘
	// （这完全取决于循环的编译方式；目的是为用户提供良好的体验
	// 用户调试程序；目标是在循环行上设置一个断点，在输入和输出时都触发
	// 和迭代）。正确处理非gofmt输入，在一个单独的系统上使用多个简单语句
	// 线路待定。
	// None
	// 优化编译将移动指令，其中一些指令将被称为坏指令
	// 用于调试目的的步骤目标（示例：寄存器溢出和重新加载；代码生成到
	// 入口块；不变代码（从循环中吊出）但这些指令仍将具有有趣的功能
	// 用于分析目的的位置。为了反映这一点，这些位置将更改为POSNOTSMT。
	// None
	// 当优化器删除标记为PosIsStmt的指令时；它应该尝试在附近找到一个
	// 将同一行标记为PosDefaultStmt作为新语句边界的指令。即
	// 优化器应该尽最大努力保存语句边界位置，并且可能会得到增强
	// 注意语句边界何时不守恒。
	// None
	// 代码克隆，例如循环展开或循环取消切换，是保护规则的一个例外
	// 因为运行调试器的用户希望在代码副本中看到活动的断点。
	// None
	// 在非优化编译中，由于代码生成，POSNOTSMT仍有一个角色
	// 进入入口区。PosIsStmt语句位置应该保持不变。
	// None
	// 生成代码时，任何剩余的默认标记位置都将替换为not语句
	// 位置。
	// None
	PosDefaultStmt uint = iota // 违约位置不是语句边界，但如果优化删除了指定的语句边界，则可能是
	PosIsStmt                  // 位置是一个语句边界；如果优化删除了相应的指令，它应该尝试找到一条新的指令作为边界。
	PosNotStmt                 // 位置不应是语句边界，但应保留行以用于分析和低级调试。
)

type PosXlogue uint

const (
	PosDefaultLogue PosXlogue = iota
	PosPrologueEnd
	PosEpilogueBegin
)

func makeLicoRaw(line, col uint) lico {
	return lico(line<<lineShift | col<<colShift)
}

// 这是一个不容忽视的立场。
// 根据调试器（gdb或delve）的不同，它可能会显示，也可能不会显示。
func makeBogusLico() lico {
	return makeLicoRaw(bogusLine, 0).withIsStmt()
}

func makeLico(line, col uint) lico {
	if line > lineMax {
		// 无法表示直线，请使用最大直线，这样我们就有了一些信息
		line = lineMax
	}
	if col > colMax {
		// 无法表示列，请使用max.column，这样我们就有了一些信息
		col = colMax
	}
	// 默认值不确定语句是否正确
	return makeLicoRaw(line, col)
}

func (x lico) Line() uint           { return uint(x) >> lineShift }
func (x lico) SameLine(y lico) bool { return 0 == (x^y)&^lico(1<<lineShift-1) }
func (x lico) Col() uint            { return uint(x) >> colShift & colMax }
func (x lico) IsStmt() uint {
	if x == 0 {
		return PosNotStmt
	}
	return uint(x) >> isStmtShift & isStmtMax
}
func (x lico) Xlogue() PosXlogue {
	return PosXlogue(uint(x) >> xlogueShift & xlogueMax)
}

// withNotStmt返回同一位置的lico，但不返回语句
func (x lico) withNotStmt() lico {
	return x.withStmt(PosNotStmt)
}

// withDefaultStmt返回同一位置的lico，默认为isStmt
func (x lico) withDefaultStmt() lico {
	return x.withStmt(PosDefaultStmt)
}

// withIsStmt返回同一位置的lico，标记为语句
func (x lico) withIsStmt() lico {
	return x.withStmt(PosIsStmt)
}

// withLogue将序言/尾声属性附加到lico
func (x lico) withXlogue(xlogue PosXlogue) lico {
	if x == 0 {
		if xlogue == 0 {
			return x
		}
		// 将0规范化为“非语句”
		x = lico(PosNotStmt << isStmtShift)
	}
	return lico(uint(x) & ^uint(xlogueMax<<xlogueShift) | (uint(xlogue) << xlogueShift))
}

// withStmt返回具有指定is_stmt属性的同一位置的lico
func (x lico) withStmt(stmt uint) lico {
	if x == 0 {
		return lico(0)
	}
	return lico(uint(x) & ^uint(isStmtMax<<isStmtShift) | (stmt << isStmtShift))
}

func (x lico) lineNumber() string {
	return fmt.Sprintf("%d", x.Line())
}

func (x lico) lineNumberHTML() string {
	if x.IsStmt() == PosDefaultStmt {
		return fmt.Sprintf("%d", x.Line())
	}
	style, pfx := "b", "+"
	if x.IsStmt() == PosNotStmt {
		style = "s" // /HTML5中不支持罢工
		pfx = ""
	}
	return fmt.Sprintf("<%s>%s%d</%s>", style, pfx, x.Line(), style)
}

func (x lico) atColumn1() lico {
	return makeLico(x.Line(), 1).withIsStmt()
}
